from collections import Counter
from operator import itemgetter, attrgetter
from functools import reduce, partial
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.figure_factory as ff
import plotly.offline as pyo
from mongoengine import connect
import src.settings as settings
from src.visualization.map import Point, Area
from src.visualization.statistics import *
from src.features.preprocessing import convert_salary
from src.data.vacancy import Vacancy
connect(
host=settings.DB_HOST,
port=settings.DB_PORT,
db=settings.DB_NAME
)
MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True, read_preference=Primary())
pyo.init_notebook_mode()
%load_ext autoreload
%autoreload 2
df: pd.DataFrame = (
Vacancy
.objects
.to_dataframe(include=[
'_id',
'name',
'description',
'salary',
'schedule.name',
'experience',
'employment.name',
'area.name',
'address.lat',
'address.lng',
'address.city',
'specializations',
'employer.name',
'professional_roles',
'key_skills',
])
)
df.set_index('_id', inplace=True)
df['salary.to'].fillna(df['salary.from'], inplace=True)
df = df[df['salary.from'].notna()]
df = df[df['salary.to'].notna()]
df = df[df['salary.currency'].notna()]
df['salary.currency'].isna().sum()
0
df.shape
(47440, 18)
df[['salary.from', 'salary.to', 'salary.currency']] = df[['salary.from', 'salary.to', 'salary.currency']].apply(
lambda row: [
convert_salary(row['salary.from'], from_currency=row['salary.currency'], db=settings.db),
convert_salary(row['salary.to'], from_currency=row['salary.currency'], db=settings.db),
row['salary.currency']
], axis=1, result_type='expand')
df['mean_salary'] = np.round((df['salary.to'] + df['salary.from']) / 2)
df[df['salary.currency'] != 'RUR'].head(10)
| description | key_skills | schedule.name | experience.id | experience.name | employment.name | salary.to | salary.from | salary.currency | salary.gross | name | area.name | employer.name | specializations | professional_roles | address.city | address.lat | address.lng | mean_salary | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| _id | |||||||||||||||||||
| 49810443 | Требуемый опыт работы: 1–3 года Частичная заня... | [Internet, Голландский язык, Работа в команде,... | Удаленная работа | between1And3 | От 1 года до 3 лет | Частичная занятость | 84674.005080 | 16934.801016 | EUR | True | Преподаватель голландского языка (онлайн) | Алматы | Lingolands | [{'id': '14.60', 'name': 'Гуманитарные науки',... | [{'id': '132', 'name': 'Учитель, преподаватель... | NaN | NaN | NaN | 50804.0 |
| 49226918 | Сеть магазинов "Соседи" приглашает на работу з... | [Управление персоналом, Пользователь ПК, Работ... | Сменный график | between1And3 | От 1 года до 3 лет | Полная занятость | 37745.710055 | 25550.942191 | BYR | False | Заведующий отделом розничных продаж | Смолевичи | СОСЕДИ, Сеть магазинов | [{'id': '17.324', 'name': 'Управление продажам... | [{'id': '127', 'name': 'Товаровед'}] | NaN | NaN | NaN | 31648.0 |
| 49225403 | Обязанности: ручная бережная и качественная м... | [] | Полный день | noExperience | Нет опыта | Полная занятость | 29035.161581 | 29035.161581 | BYR | False | Мойщик автомобилей | Минск | Такси Алмаз 7788 | [{'id': '15.390', 'name': 'Автомобильный бизне... | [{'id': '4', 'name': 'Автомойщик'}] | Минск | 53.899117 | 27.524919 | 29035.0 |
| 49225631 | Приглашаем водителей для работы в "Такси Алмаз... | [] | Гибкий график | noExperience | Нет опыта | Частичная занятость | 43552.742371 | 43552.742371 | BYR | False | Водитель Такси Алмаз 7788 | Минск | Такси Алмаз 7788 | [{'id': '21.482', 'name': 'Водитель', 'profare... | [{'id': '21', 'name': 'Водитель'}] | Минск | 53.899117 | 27.524919 | 43553.0 |
| 50155391 | Обязанности: - Обработка входящего потока сооб... | [Грамотная речь, Пользователь ПК, Работа в ком... | Удаленная работа | noExperience | Нет опыта | Полная занятость | 44716.053063 | 25339.096736 | USD | False | Менеджер по продажам в мессенджерах (Direct-ме... | Киев | Миронов Владимир | [{'id': '17.149', 'name': 'Менеджер по работе ... | [{'id': '54', 'name': 'Координатор отдела прод... | NaN | NaN | NaN | 35028.0 |
| 50157216 | Please note that by applying to this vacancy y... | [Английский язык, Coaching, Leadership Skills,... | Полный день | noExperience | Нет опыта | Стажировка | 63405.792445 | 63405.792445 | KZT | True | Sales Intern/ Стажер в отдел продаж | Алматы | Procter & Gamble | [{'id': '15.389', 'name': 'Продажи', 'profarea... | [{'id': '40', 'name': 'Другое'}] | NaN | NaN | NaN | 63406.0 |
| 50155707 | Обязанности: Запуск рекламных компаний Google,... | [Английский язык, Маркетинговый анализ, Подгот... | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 260843.642868 | 111790.132658 | USD | False | PPC specialist / Специалист по контекстной рек... | Москва | WeFix Appliance Repair | [{'id': '1.246', 'name': 'Развитие бизнеса', '... | [{'id': '68', 'name': 'Менеджер по маркетингу ... | NaN | NaN | NaN | 186317.0 |
| 50156232 | Makeomatic расширяет свою команду! Уже 7 лет м... | [Git, JavaScript, Node.js, Docker, GitHub, Dev... | Полный день | between3And6 | От 3 до 6 лет | Полная занятость | 335370.397973 | 260843.642868 | USD | True | Senior Backend Developer (Node.js) | Москва | Makeomatic Inc | [{'id': '1.221', 'name': 'Программирование, Ра... | [{'id': '96', 'name': 'Программист, разработчи... | NaN | NaN | NaN | 298107.0 |
| 50157118 | Топ-менеджер/Руководитель/Управление (банковск... | [Работа в команде, CRM, Телефонные переговоры,... | Удаленная работа | between1And3 | От 1 года до 3 лет | Полная занятость | 335370.397973 | 111790.132658 | USD | False | Руководитель (Управление, банковская сфера) | Владивосток | Красный Джин | [{'id': '9.226', 'name': 'Продажи', 'profarea_... | [{'id': '40', 'name': 'Другое'}] | NaN | NaN | NaN | 223580.0 |
| 50157432 | Julia Valler Event Staffing is a high-end mode... | [Английский язык, Работа в команде, MS PowerPo... | Полный день | noExperience | Нет опыта | Полная занятость | 74526.755105 | 74526.755105 | USD | True | Sales Manager (Full Time Remote) | США | Julia Valler Staffing | [{'id': '17.242', 'name': 'Прямые продажи', 'p... | [{'id': '70', 'name': 'Менеджер по продажам, м... | NaN | NaN | NaN | 74527.0 |
df.head(10)
| description | key_skills | schedule.name | experience.id | experience.name | employment.name | salary.to | salary.from | salary.currency | salary.gross | name | area.name | employer.name | specializations | professional_roles | address.city | address.lat | address.lng | mean_salary | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| _id | |||||||||||||||||||
| 49810439 | Обязанности: Своевременная подача автомобиля; ... | [] | Полный день | between3And6 | От 3 до 6 лет | Полная занятость | 80000.0 | 60000.0 | RUR | False | Водитель в семью | Москва | Антонова Анастасия | [{'id': '21.17', 'name': 'Автоперевозки', 'pro... | [{'id': '21', 'name': 'Водитель'}] | NaN | NaN | NaN | 70000.0 |
| 49810551 | Обязанности: Уборка дома 500 кв.м., стирка, г... | [Русский язык, Чистоплотность] | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 105000.0 | 100000.0 | RUR | False | Помощник по хозяйству на дачу | Санкт-Петербург | Агентство Прайм Домашний Персонал | [{'id': '4.494', 'name': 'Уборщица/уборщик', '... | [{'id': '130', 'name': 'Уборщица, уборщик'}] | Санкт-Петербург | 59.932428 | 30.439198 | 102500.0 |
| 49810468 | Студия Красоты и здоровья Кристалл ищет парикм... | [Пользователь ПК, Работа в команде, Грамотная ... | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 25000.0 | 25000.0 | RUR | True | Парикмахер-универсал | Волгоград | Кристалл | [{'id': '24.493', 'name': 'Парикмахер', 'profa... | [{'id': '92', 'name': 'Парикмахер'}] | NaN | NaN | NaN | 25000.0 |
| 45788942 | Условия: ЗП от 50 тысяч на руки (оклад 22 тыся... | [Складская логистика, Терминалы Сбора Данных, ... | Сменный график | noExperience | Нет опыта | Полная занятость | 80000.0 | 50000.0 | RUR | False | Кладовщик - комплектовщик | Тула | Симпл Деливери Груп | [{'id': '21.563', 'name': 'Кладовщик', 'profar... | [{'id': '131', 'name': 'Упаковщик, комплектовщ... | рабочий посёлок Горки Ленинские | 55.520630 | 37.774149 | 65000.0 |
| 49810601 | Уважаемые соискатели, рассматриваются кандидат... | [] | Полный день | moreThan6 | Более 6 лет | Полная занятость | 100000.0 | 100000.0 | RUR | False | Заместитель главного бухгалтера (производство) | Ростов-на-Дону | АнРуссТранс | [{'id': '2.335', 'name': 'Учет заработной плат... | [{'id': '18', 'name': 'Бухгалтер'}] | NaN | NaN | NaN | 100000.0 |
| 49810507 | Логопедический Пункт 1 приглашает Администрато... | [Обучение персонала, Пользователь ПК, Организа... | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 30000.0 | 25000.0 | RUR | True | Администратор детского центра | Волгоград | Логопедический Пункт №1 | [{'id': '4.332', 'name': 'Управляющий офисом (... | [{'id': '8', 'name': 'Администратор'}] | Волгоград | 48.745092 | 44.499916 | 27500.0 |
| 49810469 | Студия Красоты и здоровья Кристалл ищет парикм... | [Пользователь ПК, Работа в команде, Грамотная ... | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 25000.0 | 25000.0 | RUR | True | Парикмахер-универсал | Волжский (Волгоградская область) | Кристалл | [{'id': '24.493', 'name': 'Парикмахер', 'profa... | [{'id': '92', 'name': 'Парикмахер'}] | NaN | NaN | NaN | 25000.0 |
| 49810426 | Обязанности: выполнение услуг массажа на высок... | [антицеллюлитный, класический, спортивный, лим... | Полный день | between3And6 | От 3 до 6 лет | Полная занятость | 150000.0 | 80000.0 | RUR | True | Массажистка/массажист | Москва | ЭК Брендинг | [{'id': '24.492', 'name': 'Массажист', 'profar... | [{'id': '60', 'name': 'Массажист'}] | Москва | 55.778796 | 37.598825 | 115000.0 |
| 47003369 | Медиахолдинг "Май Медиа" ищет менеджера по про... | [Прямые продажи, Телефонные переговоры, Навыки... | Полный день | between1And3 | От 1 года до 3 лет | Полная занятость | 70000.0 | 50000.0 | RUR | False | Ведущий клиентский менеджер | Иваново (Ивановская область) | Май Медиа | [{'id': '17.242', 'name': 'Прямые продажи', 'p... | [{'id': '105', 'name': 'Руководитель отдела кл... | Иваново | 57.001064 | 40.968217 | 60000.0 |
| 43592367 | Обязанности: Запрос цен и анализ по счетам от... | [MS PowerPoint, MS Access, Работа с базами дан... | Сменный график | noExperience | Нет опыта | Полная занятость | 31500.0 | 26250.0 | RUR | True | Оператор базы данных | Белгород | My Sky | [{'id': '2.33', 'name': 'Аудит', 'profarea_id'... | [{'id': '84', 'name': 'Оператор ПК, оператор б... | Белгород | 50.576507 | 36.578904 | 28875.0 |
total_salary = df['mean_salary'].sum()
total_salary
2782463002.0
total_salary_by_area = df[['area.name', 'mean_salary']].groupby(['area.name'], as_index=False).sum().rename(
columns={'mean_salary': 'total_salary'})
total_salary_by_area.head(10)
| area.name | total_salary | |
|---|---|---|
| 0 | Абаза | 770000.0 |
| 1 | Абай | 86000.0 |
| 2 | Абакан | 8340594.0 |
| 3 | Абан | 119921.0 |
| 4 | Абатское | 195048.0 |
| 5 | Абинск | 65000.0 |
| 6 | Авсюнино | 131500.0 |
| 7 | Агалатово | 30000.0 |
| 8 | Агаповка | 86000.0 |
| 9 | Агеево | 66000.0 |
other = total_salary_by_area.total_salary < (total_salary / 100) # общая зарплата меньше 1%
other_value = total_salary_by_area.total_salary[other].agg('sum')
total_salary_by_area = total_salary_by_area[~other]
total_salary_by_area = total_salary_by_area.append({'area.name': 'Другие регионы', 'total_salary': other_value},
ignore_index=True)
total_salary_by_area
| area.name | total_salary | |
|---|---|---|
| 0 | Владивосток | 6.015154e+07 |
| 1 | Екатеринбург | 4.013486e+07 |
| 2 | Иркутск | 4.925897e+07 |
| 3 | Казань | 4.131740e+07 |
| 4 | Краснодар | 5.243045e+07 |
| 5 | Красноярск | 5.313064e+07 |
| 6 | Минск | 2.859788e+07 |
| 7 | Москва | 5.370685e+08 |
| 8 | Нижний Новгород | 3.684292e+07 |
| 9 | Новосибирск | 6.767413e+07 |
| 10 | Ростов-на-Дону | 3.439070e+07 |
| 11 | Санкт-Петербург | 2.051561e+08 |
| 12 | Уфа | 2.819120e+07 |
| 13 | Хабаровск | 4.851375e+07 |
| 14 | Другие регионы | 1.499604e+09 |
px.pie(total_salary_by_area, names='area.name', values='total_salary', title='Разделение зарплат по городам')
geo_df = df.reset_index()[['_id', 'address.city', 'address.lat', 'address.lng', 'mean_salary']]\
.groupby('address.city', as_index=False)\
.agg({'mean_salary': 'mean', '_id': 'count', 'address.lat': 'mean', 'address.lng': 'mean'})\
.rename(columns={'_id': 'count'})
px.scatter_geo(
geo_df[(geo_df['count'] > 10) & (geo_df['mean_salary'] < 200_000)],
lat='address.lat',
lon='address.lng',
size='count',
fitbounds='locations',
color='mean_salary',
hover_data=['address.city'],
center={'lat': 53, 'lon': 83},
size_max=50,
labels={'mean_salary': 'Зарплата'},
title='Количество вакансий и средняя зарплата относительно города'
)
px.histogram(
df[df.mean_salary < 500_000],
x='mean_salary',
nbins=100,
title='Распределение зарплат',
labels={'mean_salary': 'Зарплата'}
)
px.histogram(
df[df.mean_salary < 500_000],
x='mean_salary',
color='schedule.name',
nbins=100,
title='Распределение зарплат c учетом графика работы',
labels={'mean_salary': 'Зарплата', 'schedule.name': 'График'}
)
px.histogram(
df[df.mean_salary < 500_000],
x='mean_salary',
color='salary.currency',
nbins=100,
title='Распределение зарплат c учетом валюты работы',
labels={'mean_salary': 'Зарплата', 'salary.currency': 'Валюта'}
)
px.histogram(
df,
x='salary.currency',
title='Количество вакансий для каждой валюты',
labels={'salary.currency': 'Валюта'}
).update_xaxes(categoryorder='total descending')
key_skills = reduce(set.union, df.key_skills, set())
len(key_skills)
7364
key_skill_df = pd.DataFrame((
{
'Навык': key_skill,
**df[df['key_skills'].map({key_skill}.issubset)]['mean_salary']
.agg(['count', 'sum', 'mean', 'max', 'min']).to_dict()
}
for key_skill in key_skills
))
ff.create_table(key_skill_df[key_skill_df['Навык'].map(len) < 30].head(50).astype(int, errors='ignore'))
px.pie(
key_skill_df[key_skill_df['count'] > 1_000],
names='Навык',
values='count',
title='Доля вакансий для самых популярных навыков',
labels={'count': 'Количество вакансий'}
)
sorted_key_skill_df = key_skill_df.sort_values('mean', ascending=False)
px.bar(
sorted_key_skill_df.head(20),
x='Навык',
y='mean',
color='count',
title='Средняя зарплата самых высокооплачиваемых навыков',
labels={'mean': 'Средняя зарплата', 'count': 'Количество вакансий'},
range_color=(1, 3),
text_auto='.2s'
).update_xaxes(categoryorder='total descending')
px.bar(
sorted_key_skill_df[sorted_key_skill_df['count'] >= df.shape[0] * 0.008 * 0.01].head(20),
x='Навык',
y='mean',
color='count',
title='Средняя зарплата самых высокооплачиваемых навыков, необходимых не менее чем в 0.008% вакансий',
labels={'mean': 'Средняя зарплата', 'count': 'Количество вакансий'},
range_color=(1, 30),
text_auto='.2s',
height=700,
).update_xaxes(categoryorder='total descending')
px.bar(
sorted_key_skill_df[sorted_key_skill_df['count'] >= df.shape[0] * 0.1 * 0.01].head(20),
x='Навык',
y='mean',
color='count',
title='Средняя зарплата самых высокооплачиваемых навыков, необходимых не менее чем в 0.1% вакансий',
labels={'mean': 'Средняя зарплата', 'count': 'Количество вакансий'},
range_color=(1, 300),
text_auto='.2s'
).update_xaxes(categoryorder='total descending')
specs_df = df.copy()
specs_df.specializations = specs_df.specializations.map(lambda specs: list(map(itemgetter('name'), specs)))
specs_df = specs_df[specs_df.specializations.notna()]
specs_df['specialization_profarea_names'] = df.specializations.map(
lambda specs: list(set(map(itemgetter('profarea_name'), specs))))
specs_df = specs_df[specs_df.specialization_profarea_names.notna()]
specs_df['full_specializations'] = df.specializations
specs_df[['specialization_profarea_names', 'specializations']].head(10)
| specialization_profarea_names | specializations | |
|---|---|---|
| _id | ||
| 49810439 | [Транспорт, логистика] | [Автоперевозки, Водитель, Логистика, Экспедитор] |
| 49810551 | [Административный персонал, Домашний персонал] | [Уборщица/уборщик, домработница/домработник, Г... |
| 49810468 | [Спортивные клубы, фитнес, салоны красоты] | [Парикмахер] |
| 45788942 | [Транспорт, логистика] | [Кладовщик, Рабочий склада, Логистика] |
| 49810601 | [Банки, инвестиции, лизинг, Бухгалтерия, управ... | [Учет заработной платы, Основные средства, Нал... |
| 49810507 | [Административный персонал] | [Управляющий офисом (Оffice manager), Персонал... |
| 49810469 | [Спортивные клубы, фитнес, салоны красоты] | [Парикмахер] |
| 49810426 | [Спортивные клубы, фитнес, салоны красоты] | [Массажист] |
| 47003369 | [Продажи] | [Прямые продажи, Менеджер по работе с клиентами] |
| 43592367 | [Бухгалтерия, управленческий учет, финансы пре... | [Аудит, Другое, Финансовый анализ] |
all_specialization_names = list(reduce(set.union, specs_df.specializations, set()))
all_specialization_names[:15]
['Учет заработной платы', 'Системная интеграция', 'Шлифовщик', 'Основные средства', 'Корпоративное право', 'Охранник', 'Ценные бумаги', 'Авторское право', 'Журналистика', 'Фармацевтическая промышленность', 'Фотография', 'Интернет', 'Кассир, Инкассатор', 'Отопление, вентиляция и кондиционирование', 'Таможенное оформление']
len(all_specialization_names)
504
profarea_by_specialization = reduce(
dict.__or__,
map(
lambda specs:
dict(zip(
map(itemgetter('name'), specs),
map(itemgetter('profarea_name'), specs)
)),
specs_df.full_specializations
)
)
ff.create_table([('Специализация', 'Профобласть')] + list(profarea_by_specialization.items())[:50])
count_by_specialization = {spec: specs_df.specializations.map({spec}.issubset).sum() for spec in all_specialization_names}
count_by_specialization = Counter(count_by_specialization)
ff.create_table([('Специализация', 'Количество вакансий')] + count_by_specialization.most_common(10))
spec_names_df = pd.DataFrame((
{
'Специализация': spec,
'Профобласть': profarea_by_specialization[spec],
**specs_df[specs_df.specializations.map({spec}.issubset)]['mean_salary']
.agg(['count', 'sum', 'mean', 'max', 'min']).to_dict()
}
for spec in all_specialization_names
))
ff.create_table(spec_names_df[(spec_names_df['Специализация'].map(len) < 20) & (spec_names_df['Профобласть'].map(len) < 20)].head(50))
px.bar(
spec_names_df.sort_values('mean', ascending=False).head(30),
x='Специализация',
y='mean',
color='Профобласть',
labels={'mean': 'Средняя зарплата'},
title='30 самых высокооплачиваемых специализаций',
text_auto='.2s'
).update_xaxes(categoryorder='total descending')
geo_profareas_df = pd.concat(map(
pd.DataFrame,
(dict(zip(
('profarea', 'lat', 'lng'),
zip(*zip(
spec_names := set(map(itemgetter('profarea_name'), specs)),
[lat] * len(spec_names),
[lng] * len(spec_names)
))
))
for lat, lng, specs in df[['address.lat', 'address.lng', 'specializations']].dropna().values)
)).reset_index().drop('index', axis=1)
ff.create_table(geo_profareas_df.head(15))
# noinspection PyTypeChecker
points = reduce(list.__add__, [
list(map(Point.from_tuple, zip(
spec_names := set(map(itemgetter('profarea_name'), specs)),
[lat] * len(spec_names),
[lng] * len(spec_names)
)))
for lat, lng, specs in df[['address.lat', 'address.lng', 'specializations']].dropna().values
])
points[:100]
[Point(profarea='Административный персонал', lat=59.932428, lng=30.439198), Point(profarea='Домашний персонал', lat=59.932428, lng=30.439198), Point(profarea='Транспорт, логистика', lat=55.52063, lng=37.774149), Point(profarea='Административный персонал', lat=48.745092, lng=44.499916), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=55.778796, lng=37.598825), Point(profarea='Продажи', lat=57.001064, lng=40.968217), Point(profarea='Бухгалтерия, управленческий учет, финансы предприятия', lat=50.576507, lng=36.578904), Point(profarea='Строительство, недвижимость', lat=54.169395, lng=37.58279), Point(profarea='Наука, образование', lat=55.789633, lng=49.134055), Point(profarea='Домашний персонал', lat=55.789633, lng=49.134055), Point(profarea='Продажи', lat=59.947991, lng=30.268051), Point(profarea='Информационные технологии, интернет, телеком', lat=59.947991, lng=30.268051), Point(profarea='Транспорт, логистика', lat=56.358208, lng=37.505678), Point(profarea='Рабочий персонал', lat=56.358208, lng=37.505678), Point(profarea='Административный персонал', lat=56.836101, lng=60.614578), Point(profarea='Продажи', lat=56.836101, lng=60.614578), Point(profarea='Продажи', lat=55.689695, lng=37.467482), Point(profarea='Продажи', lat=55.8623392366, lng=37.5676616141), Point(profarea='Строительство, недвижимость', lat=44.740913, lng=37.724526), Point(profarea='Медицина, фармацевтика', lat=53.499701, lng=49.275127), Point(profarea='Продажи', lat=44.740913, lng=37.724526), Point(profarea='Административный персонал', lat=55.558208, lng=37.695501), Point(profarea='Продажи', lat=55.558208, lng=37.695501), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=55.558208, lng=37.695501), Point(profarea='Искусство, развлечения, масс-медиа', lat=59.9410372053, lng=30.2767724527), Point(profarea='Маркетинг, реклама, PR', lat=59.9410372053, lng=30.2767724527), Point(profarea='Туризм, гостиницы, рестораны', lat=59.935638, lng=30.361673), Point(profarea='Продажи', lat=55.039068, lng=82.971696), Point(profarea='Продажи', lat=55.890239, lng=37.422548), Point(profarea='Административный персонал', lat=54.903112, lng=52.304937), Point(profarea='Продажи', lat=54.903112, lng=52.304937), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=54.903112, lng=52.304937), Point(profarea='Медицина, фармацевтика', lat=55.439242, lng=37.56635), Point(profarea='Управление персоналом, тренинги', lat=53.184521, lng=50.104092), Point(profarea='Медицина, фармацевтика', lat=55.439242, lng=37.56635), Point(profarea='Транспорт, логистика', lat=55.750358, lng=37.755895), Point(profarea='Строительство, недвижимость', lat=51.686534, lng=39.256746), Point(profarea='Продажи', lat=51.686534, lng=39.256746), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=55.856341, lng=37.493847), Point(profarea='Туризм, гостиницы, рестораны', lat=59.928438, lng=30.35887), Point(profarea='Административный персонал', lat=59.928438, lng=30.35887), Point(profarea='Туризм, гостиницы, рестораны', lat=59.7999429934, lng=30.271653709), Point(profarea='Автомобильный бизнес', lat=56.129504, lng=47.300037), Point(profarea='Медицина, фармацевтика', lat=54.715909, lng=20.484795), Point(profarea='Начало карьеры, студенты', lat=53.899117, lng=27.524919), Point(profarea='Автомобильный бизнес', lat=53.899117, lng=27.524919), Point(profarea='Транспорт, логистика', lat=55.825391, lng=37.353971), Point(profarea='Начало карьеры, студенты', lat=47.21933, lng=39.713827), Point(profarea='Продажи', lat=47.21933, lng=39.713827), Point(profarea='Строительство, недвижимость', lat=45.014256, lng=38.956242), Point(profarea='Производство, сельское хозяйство', lat=45.014256, lng=38.956242), Point(profarea='Продажи', lat=55.531188, lng=37.620465), Point(profarea='Инсталляция и сервис', lat=56.079904, lng=93.011349), Point(profarea='Производство, сельское хозяйство', lat=56.079904, lng=93.011349), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=55.829593, lng=37.371164), Point(profarea='Транспорт, логистика', lat=53.899117, lng=27.524919), Point(profarea='Административный персонал', lat=56.830822, lng=60.597501), Point(profarea='Информационные технологии, интернет, телеком', lat=55.785979, lng=37.660521), Point(profarea='Искусство, развлечения, масс-медиа', lat=55.785979, lng=37.660521), Point(profarea='Маркетинг, реклама, PR', lat=55.785979, lng=37.660521), Point(profarea='Продажи', lat=50.59399, lng=36.600563), Point(profarea='Административный персонал', lat=61.643647, lng=50.823454), Point(profarea='Туризм, гостиницы, рестораны', lat=45.074572, lng=41.941191), Point(profarea='Административный персонал', lat=45.074572, lng=41.941191), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=56.881502, lng=60.51041), Point(profarea='Административный персонал', lat=59.856829603, lng=30.315072455), Point(profarea='Начало карьеры, студенты', lat=60.0089569106, lng=30.2589382952), Point(profarea='Туризм, гостиницы, рестораны', lat=60.0089569106, lng=30.2589382952), Point(profarea='Домашний персонал', lat=55.659231, lng=37.560835), Point(profarea='Административный персонал', lat=56.331972, lng=36.733432), Point(profarea='Медицина, фармацевтика', lat=56.331972, lng=36.733432), Point(profarea='Производство, сельское хозяйство', lat=55.873203, lng=37.122619), Point(profarea='Транспорт, логистика', lat=55.631773, lng=37.618031), Point(profarea='Безопасность', lat=55.775388, lng=37.657817), Point(profarea='Юристы', lat=55.853718, lng=48.568126), Point(profarea='Медицина, фармацевтика', lat=59.957782, lng=30.342189), Point(profarea='Административный персонал', lat=51.535389, lng=46.01815), Point(profarea='Продажи', lat=51.535389, lng=46.01815), Point(profarea='Спортивные клубы, фитнес, салоны красоты', lat=51.535389, lng=46.01815), Point(profarea='Транспорт, логистика', lat=55.705645, lng=37.689447), Point(profarea='Рабочий персонал', lat=55.705645, lng=37.689447), Point(profarea='Транспорт, логистика', lat=55.794067, lng=49.169098), Point(profarea='Административный персонал', lat=43.673405, lng=40.297525), Point(profarea='Административный персонал', lat=44.9984885793, lng=39.0794369012), Point(profarea='Производство, сельское хозяйство', lat=55.726396, lng=37.381801), Point(profarea='Рабочий персонал', lat=55.726396, lng=37.381801), Point(profarea='Туризм, гостиницы, рестораны', lat=53.348763, lng=83.777009), Point(profarea='Начало карьеры, студенты', lat=47.270275, lng=39.72617), Point(profarea='Продажи', lat=47.270275, lng=39.72617), Point(profarea='Юристы', lat=55.775545, lng=37.586077), Point(profarea='Производство, сельское хозяйство', lat=55.853895, lng=37.517734), Point(profarea='Административный персонал', lat=47.236788, lng=39.614797), Point(profarea='Банки, инвестиции, лизинг', lat=55.863328, lng=37.540093), Point(profarea='Бухгалтерия, управленческий учет, финансы предприятия', lat=55.863328, lng=37.540093), Point(profarea='Рабочий персонал', lat=55.879101, lng=38.479811), Point(profarea='Бухгалтерия, управленческий учет, финансы предприятия', lat=55.73441, lng=37.59047), Point(profarea='Строительство, недвижимость', lat=59.910641, lng=30.267404), Point(profarea='Начало карьеры, студенты', lat=55.719536, lng=37.628155), Point(profarea='Туризм, гостиницы, рестораны', lat=55.719536, lng=37.628155), Point(profarea='Маркетинг, реклама, PR', lat=54.953918, lng=82.878056)]
areas = reduce(lambda l, p: l + [Area(p.profarea, [p])] if not any(list(map(partial(Area.__iand__, other=p), l))) else l, points, [])
geo_profareas_grouped_df = pd.DataFrame(zip(
map(attrgetter('profarea'), areas),
map(attrgetter('count'), areas),
map(attrgetter('center_lat'), areas),
map(attrgetter('center_lng'), areas),
), columns=['profarea', 'count', 'center_lat', 'center_lng'])
geo_profareas_grouped_df.head(10)
| profarea | count | center_lat | center_lng | |
|---|---|---|---|---|
| 0 | Административный персонал | 229 | 59.904215 | 30.321857 |
| 1 | Домашний персонал | 32 | 59.981839 | 30.317089 |
| 2 | Транспорт, логистика | 887 | 55.724103 | 37.582520 |
| 3 | Административный персонал | 23 | 48.348426 | 44.214197 |
| 4 | Спортивные клубы, фитнес, салоны красоты | 337 | 55.717553 | 37.631853 |
| 5 | Продажи | 2771 | 55.773011 | 37.838572 |
| 6 | Бухгалтерия, управленческий учет, финансы пред... | 25 | 51.704838 | 37.703753 |
| 7 | Строительство, недвижимость | 40 | 52.520757 | 39.150968 |
| 8 | Наука, образование | 11 | 55.558580 | 49.238237 |
| 9 | Домашний персонал | 8 | 54.450943 | 49.326572 |
len(areas)
1848
px.scatter_geo(
geo_profareas_grouped_df,
lat='center_lat',
lon='center_lng',
size='count',
fitbounds='locations',
color='profarea',
center={'lat': 53, 'lon': 83},
size_max=50,
labels={'count': 'Количество вакансий'},
title='Количество вакансий и средняя зарплата относительно города'
)
all_profareas = reduce(set.union, specs_df.specialization_profarea_names, set())
len(all_profareas)
28
{profarea: specs_df[specs_df.specialization_profarea_names.map({profarea}.issubset)]['mean_salary'].mean() for profarea
in all_profareas}
{'Высший менеджмент': 123710.24584717608,
'Закупки': 62442.23834886817,
'Управление персоналом, тренинги': 65502.94584382872,
'Инсталляция и сервис': 61575.194444444445,
'Спортивные клубы, фитнес, салоны красоты': 51376.46395250212,
'Консультирование': 92617.73353751915,
'Безопасность': 53167.152825836216,
'Домашний персонал': 41872.405241935485,
'Начало карьеры, студенты': 43474.821203244705,
'Производство, сельское хозяйство': 64457.76760048721,
'Добыча сырья': 94832.55590062111,
'Бухгалтерия, управленческий учет, финансы предприятия': 48283.09805153991,
'Строительство, недвижимость': 84771.85741049125,
'Автомобильный бизнес': 72177.20721925133,
'Искусство, развлечения, масс-медиа': 56821.15873015873,
'Туризм, гостиницы, рестораны': 47756.217606707316,
'Наука, образование': 45231.578881987574,
'Страхование': 72418.31764705882,
'Рабочий персонал': 64773.36851851852,
'Маркетинг, реклама, PR': 60350.047784967646,
'Продажи': 49176.52389049481,
'Государственная служба, некоммерческие организации': 52380.44400785855,
'Юристы': 55641.35897435898,
'Банки, инвестиции, лизинг': 57242.99230111206,
'Административный персонал': 48667.45219370861,
'Информационные технологии, интернет, телеком': 92008.00538176925,
'Транспорт, логистика': 67427.33646295663,
'Медицина, фармацевтика': 55659.44958753437}
df_profarea = pd.DataFrame({
profarea: specs_df[
specs_df.specialization_profarea_names
.map({profarea}.issubset)
]['mean_salary'].agg(['count', 'sum', 'mean', 'max', 'min'])
for profarea in all_profareas
}).T
df_profarea = df_profarea.astype(np.int64).reset_index().rename(columns={'index': 'profarea'})
df_profarea.head(10)
| profarea | count | sum | mean | max | min | |
|---|---|---|---|---|---|---|
| 0 | Высший менеджмент | 602 | 74473568 | 123710 | 1200000 | 150 |
| 1 | Закупки | 751 | 46894121 | 62442 | 300000 | 1 |
| 2 | Управление персоналом, тренинги | 794 | 52009339 | 65502 | 525000 | 10000 |
| 3 | Инсталляция и сервис | 468 | 28817191 | 61575 | 200000 | 1300 |
| 4 | Спортивные клубы, фитнес, салоны красоты | 1179 | 60572851 | 51376 | 1001000 | 3000 |
| 5 | Консультирование | 653 | 60479380 | 92617 | 1650000 | 12500 |
| 6 | Безопасность | 1734 | 92191843 | 53167 | 338696 | 1500 |
| 7 | Домашний персонал | 496 | 20768713 | 41872 | 250000 | 1300 |
| 8 | Начало карьеры, студенты | 8876 | 385882513 | 43474 | 550000 | 30 |
| 9 | Производство, сельское хозяйство | 4105 | 264599136 | 64457 | 650000 | 1 |
px.bar(
df_profarea,
x='profarea',
y='count',
labels={'profarea': 'Профобласть', 'count': 'Количество вакансий'},
text_auto='.2s',
title='Количество вакансий в каждой области'
).update_xaxes(categoryorder='total descending')
px.pie(
df_profarea,
names='profarea',
values='count',
labels={'index': 'Профобласть', 'count': 'Количество вакансий'},
title='Доля вакансий для каждой области'
)
px.bar(
df_profarea,
x='profarea',
y='mean',
labels={'profarea': 'Профобласть', 'mean': 'Средняя зарплата'},
text_auto='.2s',
title='Средняя зарплата в каждой области'
).update_xaxes(categoryorder='total descending')
px.bar(
df_profarea,
x='profarea',
y='sum',
labels={'profarea': 'Профобласть', 'sum': 'Сумма всех зарплат'},
text_auto='.2s',
title='Сумма зарплат в каждой области'
).update_xaxes(categoryorder='total descending')